#include "xsdp.h"

#include <string.h>

#include "codecs.hpp"
#include "xm_log.h"

#define sdp_dbgi(FMT, ARGS...) dbgi("|%s| " FMT, "sdp", ##ARGS )


static
char * next_line_end(char * p){
    while(*p != '\0' && *p != '\r' && *p != '\n'){
        p++;
    }
    return p;
}

static
char * next_line_begin(char *p){
    while(*p == '\r' || *p == '\n'){
        if(*p == '\0') break;
        p++;
    }
    return p;
}

static
char * next_non_space(char *p){
    while(*p != '\0' && (*p == ' ' || *p == '\t')){
        p++;
    }
    return p;
}

static
char * next_space(char *p){
    while(*p != '\0' && (*p != ' ' && *p != '\t')){
        p++;
    }
    return p;
}

static
char * next_char(char *p, char c){
    while(*p != '\0' && (*p != c)){
        p++;
    }
    return p;
}


static
void dummy_free(void * ){
}

void xsdp_stream_free(xsdp_stream * stream){
    if(!stream) return ;
    if(stream->str_candidates){
        g_slist_free_full(stream->str_candidates, dummy_free);
        stream->str_candidates = NULL;
    }
//    if (stream->payloads) {
//        g_slist_free(stream->payloads);
//        stream->payloads = nullptr;
//    }
    if (stream->rtp_exten_map) {
        RtpExtensionMap::destroy(stream->rtp_exten_map);
        stream->rtp_exten_map = nullptr;
    }
    if(stream->rtpmaps){
        g_slist_free_full(stream->rtpmaps, g_free);
        stream->rtpmaps = NULL;
    }

    if (stream->fmtps) {
        g_slist_free_full(stream->fmtps, g_free);
        stream->fmtps = NULL;
    }
}

void xsdp_free_info(xsdpinfo * info){
    if(!info) return;
    
    if(info->audio_stream){
        xsdp_stream_free(info->audio_stream);
        info->audio_stream = NULL;
    }
    
    if(info->video_stream){
        xsdp_stream_free(info->video_stream);
        info->video_stream = NULL;
    }
    
    if(info->sdpbuf){
        g_free(info->sdpbuf); info->sdpbuf = nullptr;
    }

    if(info->sdp_bundles){
        g_free(info->sdp_bundles); info->sdp_bundles = nullptr;
    }

    if (info->sdpbuf_orig) {
        g_free(info->sdpbuf_orig); info->sdpbuf_orig = nullptr;
    }

    if (info->codecs) {
        rtc_codecs_free(info->codecs);
        info->codecs = nullptr;
    }
    g_free(info);
}

void xsdp_stream_print(xsdp_stream * stream, const char * prefix){
    int candidate_num = stream->str_candidates ? g_slist_length(stream->str_candidates) : 0;
    sdp_dbgi("%s-> stream=%s, rtcpmux=%d, ufrag=[%s], pwd=[%s], hash=[%s], finger=[%s], ssrcs=%d, candidates=%d, dir=%s", prefix, stream->stream_name
         , stream->is_rtcpmux
         , stream->ice_ufrag?stream->ice_ufrag:""
         , stream->ice_pwd?stream->ice_pwd:""
         , stream->fingerprint_hash_name?stream->fingerprint_hash_name:""
         , stream->fingerprint_str?stream->fingerprint_str:""
         , stream->ssrc_count
         , candidate_num
         , stream->sendrecv == xsdp_sendrecv_type::sendrecv ? "sendrecv" : stream->sendrecv == xsdp_sendrecv_type::sendonly ? "sendonly" : "recvonly");
    
    GSList * item = stream->rtpmaps;
    for(int n = 0; item;){
        n++;
        xsdp_rtpmap * d = (xsdp_rtpmap *)item->data;
        sdp_dbgi("%s--> rtpmap[%d]: name=[%s], pt=%d, freq=%d, channels=%d", prefix, n, d->name, d->payload_type, d->freq, d->channels);
        item = g_slist_next(item);
    }

    item = stream->fmtps;
    while (item) {
        xsdp_fmtp* d = (xsdp_fmtp*)item->data;
        switch (d->type) {
            case xsdp_fmtp_type::apt:
                sdp_dbgi("%s--> fmtp[%d]: name=[apt], value=%d", prefix, d->pt, d->apt);
                break;
            case xsdp_fmtp_type ::minptime:
                sdp_dbgi("%s--> fmtp[%d]: name=[minptime], value=%d", prefix, d->pt, d->minptime);
                break;
            case xsdp_fmtp_type ::useinbandfec:
                sdp_dbgi("%s--> fmtp[%d]: name=[useinbandfec], value=%d", prefix, d->pt, d->useinbandfec);
                break;
            case xsdp_fmtp_type ::levelAsymmetryAllowed:
                sdp_dbgi("%s--> fmtp[%d]: name=[level-asymmetry-allowed], value=%d", prefix, d->pt, d->levelAsymmetryAllowed);
                break;
            case xsdp_fmtp_type ::packetizationMode:
                sdp_dbgi("%s--> fmtp[%d]: name=[packetization-mode], value=%d", prefix, d->pt, d->packetizationMode);
                break;
            case xsdp_fmtp_type ::profileLevelId:
                sdp_dbgi("%s--> fmtp[%d]: name=[profile-level-id], value=%s", prefix, d->pt, d->profileLevelId);
                break;
        }
        item = g_slist_next(item);
    }
    
    for(int i = 0; i < stream->ssrc_count; i++){
        sdp_dbgi("%s--> ssrc[%d]: [%u]", prefix, i+1, stream->ssrcs[i]);
    }
    
    GSList * cand = stream->str_candidates;
    for(int n = 0; cand;){
        n++;
        char * str = (char *)cand->data;
        sdp_dbgi("%s--> cand[%d]: [%s]", prefix, n, str);
        cand = g_slist_next(cand);
    }
    
}

void xsdp_print(xsdpinfo *info, const char * prefix){
    if(!prefix ) prefix = "";
    sdp_dbgi("%s: bundle=%d, streams=%d", prefix, info->is_bundle, info->stream_count);
    if(info->audio_stream){
        xsdp_stream_print(info->audio_stream, "");
    }
    
    if(info->video_stream){
        xsdp_stream_print(info->video_stream, "");
    }
}

int xsdp_parse(const char * sdpdesc, int sdp_len, xsdpinfo **pinfo){
    xsdpinfo * info  = NULL;
    int ret = -1;
    do{
        if(sdp_len <= 3){
            ret = -2;
            break;
        }
        
        if(!g_str_has_prefix (sdpdesc, "v=0")){
            ret = -10;
            break;
        }

        info = (xsdpinfo *) g_malloc0(sizeof(xsdpinfo));
        info->sdpbuf = (char *) g_malloc0( sdp_len+1 );
        info->sdp_len = sdp_len + 1;
        memcpy(info->sdpbuf, sdpdesc, sdp_len);
        info->sdpbuf[sdp_len] = '\0';
        // copy the sdp before we modified it
        info->sdpbuf_orig = (char*)g_malloc0(sdp_len + 1);
        memcpy(info->sdpbuf_orig, sdpdesc, sdp_len);
        info->sdpbuf_orig[sdp_len] = '\0';
        // set default values for xsdpinfo
        info->audio_stream_storage.sendrecv = xsdp_sendrecv_type::sendrecv;
        info->video_stream_storage.sendrecv = xsdp_sendrecv_type::sendrecv;
        xsdp_stream * current_stream = NULL;
        unsigned int current_ssrc = 0;
        char *p = info->sdpbuf;
        char * line_begin;
        char * line_end;
        int line_count = 0;
        int mlineindex = 0;
        ret = 0;
        for(; *p ; line_count++){
            line_begin = p;
            line_end = next_line_end(line_begin);
            p = next_line_begin(line_end);
            
            int line_len = line_end - line_begin;
            if(line_len == 0){ // empty line
                continue;
            }
            if(line_len == 1){
                ret = -20;
                break;
            }
            if(line_begin[1] != '='){
                ret = 30;
                break;
            }
            *line_end = '\0';
            //            sdp_dbgi("line %02d: %s", line_count+1, line_begin);
            
            char field = line_begin[0];
            if(field == 'm'){
                if(g_str_has_prefix (line_begin+2, "audio")){
                    if(info->audio_stream){
                        ret = -61;
                        break;
                    }
                    info->audio_stream = &info->audio_stream_storage;
                    current_stream = info->audio_stream;
                    memset(current_stream, 0, sizeof(xsdp_stream));
                    current_stream->stream_name = "audio";
                    current_stream->mlineindex = mlineindex;
                    char* sp = next_space(line_begin+7);
                    if (sp >= line_end) {
                        ret = -63;
                        break;
                    }
                    sp = next_space(sp + 1);
                    if (sp >= line_end) {
                        ret = -63;
                        break;
                    }
                    sp = next_space(sp + 1);
                    if (sp >= line_end) {
                        ret = -63;
                        break;
                    }
                    char* payload = sp + 1;
                    while (payload < line_end) {
                        char* next_sp = next_space(payload + 1);
                        *next_sp = '\0';
                        uint8_t codec_pt = static_cast<uint8_t>(atoi(payload));
                        rtc_codec_t * c = rtc_codec_new();
                        c->pt = codec_pt;
                        if (!info->codecs) info->codecs = rtc_codecs_new();
                        rtc_codecs_add(info->codecs, c);
                        payload = next_sp + 1;
                    }
                    info->stream_count++;
                }else if(g_str_has_prefix (line_begin+2, "video") ){
                    if(info->video_stream){
                        ret = -62;
                        break;
                    }
                    info->video_stream = &info->video_stream_storage;
                    current_stream = info->video_stream;
                    memset(current_stream, 0, sizeof(xsdp_stream));
                    current_stream->stream_name = "video";
//                    current_stream->stream_id = 2;
                    current_stream->mlineindex = mlineindex;
                    // m=video 9 UDP/TLS/RTP/SAVPF 100 96 98
                    char* sp = next_space(line_begin+7);
                    if (sp >= line_end) {
                        ret = -63;
                        break;
                    }
                    sp = next_space(sp + 1);
                    if (sp >= line_end) {
                        ret = -63;
                        break;
                    }
                    sp = next_space(sp + 1);
                    if (sp >= line_end) {
                        ret = -63;
                        break;
                    }
                    char* payload = sp + 1;
                    while (payload < line_end) {
                        char* next_sp = next_space(payload + 1);
                        *next_sp = '\0';
                        uint8_t codec_pt = static_cast<uint8_t>(atoi(payload));
                        rtc_codec_t * c = rtc_codec_new();
                        c->pt = codec_pt;
                        if (!info->codecs) info->codecs = rtc_codecs_new();
                        rtc_codecs_add(info->codecs, c);
//                        current_stream->payloads = g_slist_prepend(current_stream->payloads, reinterpret_cast<void*>(atoi(payload)));
                        payload = next_sp + 1;
                    }
                    info->stream_count++;
                }else if(g_str_has_prefix (line_begin+2, "application")){
                    //m=application 0 DTLS/SCTP 0
                    current_stream = NULL;
                }else{
                    ret = -70;
                    break;
                }
                mlineindex++;
            }else{
                // stream parameters
                
                if(!current_stream) {
                    if(g_str_has_prefix (line_begin, "a=group:BUNDLE")){
                        // a=group:BUNDLE audio video
                        if(info->stream_count > 0){
                            ret = -41;
                            break;
                        }
                        // TODO: check audio and video
                        info->is_bundle = 1;

                        info->sdp_bundles = (char *) g_malloc0(line_len+1);
                        info->sdp_bundles_len = line_len + 1;
                        memcpy(info->sdp_bundles, line_begin, line_len);
                        info->sdp_bundles[line_len] = '\0';
                        
                    }
                    continue;
                }
                
                
                if(field == 'a'){
                    if(g_str_has_prefix (line_begin+2, "candidate:")){
                        // a=candidate:556447776 1 udp 2122260223 172.17.2.253 53023 typ host generation 0
                        current_stream->str_candidates = g_slist_prepend (current_stream->str_candidates, line_begin);
                    }else if(g_str_has_prefix (line_begin+2, "mid:")){
                        current_stream->mid = line_begin + 6;
                    }else if(g_str_has_prefix (line_begin+2, "ice-ufrag:")){
                        // a=ice-ufrag:BF1+05rDZZJoDriO
                        current_stream->ice_ufrag = line_begin + 12;
                    }else if(g_str_has_prefix (line_begin+2, "ice-pwd:")){
                        // a=ice-pwd:w/HRNoYWomfn556n/U9P4sDJ
                        current_stream->ice_pwd = line_begin + 10;
                    }else if(g_str_has_prefix (line_begin+2, "fingerprint:")){
                        // a=fingerprint:sha-256 76:40:CD:3B:48:A0:E1:36:20:D2:7C:98:17:0B:26:C6:3D:10:6B:C1:99:59:7F:BD:BF:84:70:9B:41:55:D4:96
                        current_stream->fingerprint_hash_name = line_begin+14;
                        char * next_sp = next_space(line_begin+14);
                        current_stream->fingerprint_str = next_non_space(next_sp);
                        *next_sp = '\0';
                    }else if(g_str_has_prefix (line_begin+2, "rtcp-mux")){
                        // a=rtcp-mux
                        current_stream->is_rtcpmux = 1;
                    }else if(g_str_has_prefix (line_begin+2, "ssrc-group:FID")){
                        /*
                         * 	a=ssrc-group:FID 586466331 2053167359 (SSRC SSRC-rtx)
                         * SSRC group FID: https://tools.ietf.org/html/rfc3388#section-7 */
                        // TODO:
                    }else if(g_str_has_prefix (line_begin+2, "ssrc:")){
                        //                    a=ssrc:2416703250 cname:OK+zmlFC2eIsZZql
                        char * str = line_begin+7;
                        char * next_sp = next_space(str);
                        *next_sp = '\0';
                        unsigned int ssrc = (unsigned int)atol(str);
                        if(ssrc != current_ssrc){
                            current_ssrc = ssrc;
                            if(current_stream->ssrc_count < XSDP_MAX_SSRCS){
                                // this could be wrong if a=ssrc is interleaved,
                                // the correct way is to use ssrc-group instead
                                current_stream->ssrcs[current_stream->ssrc_count] = ssrc;
                                current_stream->ssrc_count++;
                            }
                        }
                    }else if(g_str_has_prefix (line_begin+2, "rtpmap:")){
                        //                        a=rtpmap:111 opus/48000/2
                        //                        a=rtpmap:100 VP8/90000
                        uint8_t codec_pt = 0;
                        char* codec_name = nullptr;
                        uint32_t codec_clockrate = 0;
                        uint8_t codec_channel_nb = 1;
                        // search for pt
                        char * nextp = line_begin+9;
                        char * str = nextp;
                        nextp = next_space(nextp);
                        *nextp = '\0';
                        codec_pt = static_cast<uint8_t>(atoi(str));
                        if(nextp == line_end){
                            ret = -81;
                            break;
                        }
                        // search for name
                        nextp = next_non_space(nextp+1);
                        codec_name = nextp;
                        nextp = next_char(nextp, '/');
                        *nextp = '\0';
                        if(nextp == line_end){
                            ret = -82;
                            break;
                        }
                        // search for clockrate and channel nb
                        str = next_non_space(nextp+1);
                        nextp = next_char(str, '/');
                        if(*nextp){
                            *nextp = '\0';
                            codec_clockrate = static_cast<uint32_t>(atoi(str));
                            codec_channel_nb = static_cast<uint8_t>(atoi(nextp + 1));
                        }else{
                            codec_clockrate = static_cast<uint32_t>(atoi(str));
                            codec_channel_nb = 1;
                        }
                        rtc_codec_id_t codec_id = rtc_codec_id(codec_name);
                        rtc_codec_t* codec = rtc_codecs_find_by_pt(info->codecs, codec_pt);
                        if (codec) {
                            codec->id = codec_id;
                            codec->clock_rate = codec_clockrate;
                            codec->channel_nb = codec_channel_nb;
                        }
                    } else if (g_str_has_prefix (line_begin+2, "fmtp:")) {
                        //   a=fmtp:101 0-15
                        //   a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
                        char* nextp = line_begin + 7;
                        char* pt_string = nextp;
                        nextp = next_space(nextp);
                        *nextp = '\0';
                        uint8_t codec_pt = static_cast<uint8_t>(atoi(pt_string));
                        rtc_codec_t* codec = rtc_codecs_find_by_pt(info->codecs, codec_pt);
                        if (nextp == line_end) {
                            ret = -85;
                            break;
                        }
                        while (nextp != line_end) {
                            nextp = next_non_space(nextp + 1);
                            if (nextp == line_end) {
                                break;
                            }
                            char* value = nextp;
                            nextp = next_char(nextp + 1, ';');
                            if (nextp != line_end) {
                                *nextp = '\0';
                            }
                            if (codec) {
                                codec->caps.emplace_back(value);
                            }

                        }
                    } else if (g_str_has_prefix(line_begin + 2, "rtcp-fb:")) {
                        // a=rtcp-fb:96 ccm fir
                        char* nextp = line_begin + 10;
                        char* pt_string = nextp;
                        nextp = next_space(nextp);
                        *nextp = '\0';
                        if (nextp == line_end) {
                            continue;
                        }
                        uint8_t codec_pt = static_cast<uint8_t>(atoi(pt_string));
                        rtc_codec_t* codec = rtc_codecs_find_by_pt(info->codecs, codec_pt);
                        if (codec) codec->feedbacks.emplace_back(std::string(nextp + 1));
                    } else if (g_str_has_prefix(line_begin+2, "sendrecv")) {
                        if (current_stream) {
                            current_stream->sendrecv = xsdp_sendrecv_type::sendrecv;
                        }
                    } else if (g_str_has_prefix(line_begin+2, "sendonly")) {
                        if (current_stream) {
                            current_stream->sendrecv = xsdp_sendrecv_type::sendonly;
                        }
                    } else if (g_str_has_prefix(line_begin+2, "recvonly")) {
                        if (current_stream) {
                            current_stream->sendrecv = xsdp_sendrecv_type::recvonly;
                        }
                    } else if (g_str_has_prefix(line_begin+2, "extmap")) {
                        char* id_str = line_begin+9;
                        char* space = next_space(line_begin+9);
                        if (space == line_end) {
                            continue;
                        }
                        char* uri_str = next_non_space(space);
                        if (uri_str == line_end) {
                            continue;
                        }
                        *space = '\0';
                        int id = atoi(id_str);
                        if (id <= 0 || id > 15) {
                            dbgi("parsing sdp extmap: invalid id %d with uri %s", id, uri_str);
                            continue;
                        }
                        if (current_stream) {
                            if (current_stream->rtp_exten_map == nullptr) {
                                current_stream->rtp_exten_map = RtpExtensionMap::create();
                            }
                            current_stream->rtp_exten_map->add(uri_str, id);
                        }
                    }
                } // end of a
            } // end of non m
        } // end of for

        if(ret) break;
        
        if(info->audio_stream){
            if(!info->audio_stream->mid){
                info->audio_stream->mid = info->audio_stream->stream_name;
            }
        }
        if(info->video_stream){
            if(!info->video_stream->mid){
                info->video_stream->mid = info->video_stream->stream_name;
            }
        }
        if (!info->codecs) {
            dbge("sdp contains no codecs info");
            ret = -90;
            break;
        }
        *pinfo = info;
        ret = 0;
    }while(0);
    if(ret){
        xsdp_free_info(info);
        info = NULL;
    }
    return ret;
}

// rtc->rtc_role == RTC_ROLE_OFFER ? "actpass" : "active"

size_t xsdp_format_audio_sdp(char *buf, size_t len, RtpExtensionMap* extens, rtc_codecs_t *codecs, const char *mid, const char *setup,
                             const char *usr, const char *pwd, const char *key, const char *sendrecv, bool mux_rtcp,
                             uint32_t ssrc, const char *cname, const char *stream, const char *track) {
    size_t offset = 0;
    offset += snprintf(buf + offset, len - offset, "m=audio 9 RTP/SAVPF");
    for (auto codec : codecs->codecs) {
        if (rtc_codec_type(codec->id) != rtc_codec_type_audio) continue;
        offset += snprintf(buf + offset, len - offset, " %d", codec->pt);
    }
    offset += snprintf(buf + offset, len - offset, "\r\n");
    offset += snprintf(buf + offset, len - offset, "a=rtcp:9 IN IP4 0.0.0.0\r\n");
    if (extens) {
        extens->for_each([&buf, &len, &offset](RtpExtensionType type, int id) {
            offset += snprintf(buf + offset, len - offset, "a=extmap:%d %s\r\n", id, RtpExtensionMap::uris()[type]);
        });
    }

    // chrome:
    //   offset += snprintf(buf + offset, len - offset, "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n");
    // firefox:
    //   offset += snprintf(buf + offset, len - offset, "a=extmap:1/sendonly urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n");

    offset += snprintf(buf + offset, len - offset, "a=maxptime:60\r\n");
    offset += snprintf(buf + offset, len - offset, "a=setup:%s\r\n", setup);
    offset += snprintf(buf + offset, len - offset, "a=mid:%s\r\n", mid);
    offset += snprintf(buf + offset, len - offset, "a=ice-ufrag:%s\r\n", usr);
    offset += snprintf(buf + offset, len - offset, "a=ice-pwd:%s\r\n", pwd);
    offset += snprintf(buf + offset, len - offset, "a=fingerprint:sha-256 %s\r\n", key);
    offset += snprintf(buf + offset, len - offset, "a=%s\r\n", sendrecv);
    if (mux_rtcp) offset += snprintf(buf + offset, len - offset, "a=rtcp-mux\r\n");
    for (auto codec : codecs->codecs) {
        if (rtc_codec_type(codec->id) != rtc_codec_type_audio) continue;
        offset += snprintf(buf + offset, len - offset, "a=rtpmap:%d %s/%d/%d\r\n", codec->pt, rtc_codec_name(codec->id), codec->clock_rate, codec->channel_nb);
        if (codec->caps.size()) {
            offset += snprintf(buf + offset, len - offset, "a=fmtp:%d ", codec->pt);
            auto cap_it = codec->caps.begin();
            while (cap_it != codec->caps.end()) {
                auto& cap = *cap_it;
                offset += snprintf(buf + offset, len - offset, "%s", cap.c_str());
                ++cap_it;
                if (cap_it != codec->caps.end()) offset += snprintf(buf + offset, len - offset, ";");
            }
            offset += snprintf(buf + offset, len - offset, "\r\n");
        }
    }
    offset += snprintf(buf + offset, len - offset, "a=ssrc:%u cname:%s\r\n", ssrc, cname);
    offset += snprintf(buf + offset, len - offset,"a=ssrc:%u msid:%s %s\r\n", ssrc, stream, track);
    offset += snprintf(buf + offset, len - offset,"a=ssrc:%u mslabel:%s\r\n", ssrc, stream);
    offset += snprintf(buf + offset, len - offset,"a=ssrc:%u label:%s\r\n", ssrc, track);
    return offset;
}

size_t
xsdp_format_video_sdp(char *buf, size_t len, RtpExtensionMap* extens, rtc_codecs_t *codecs, bool enable_fec, int bitrate_max, int bitrate_min,
                      const char *mid, const char *setup, const char *usr, const char *pwd,
                      const char *key, const char *sendrecv, bool mux_rtcp, uint32_t ssrc_pri, uint32_t ssrc_rtx,
                      const char *cname, const char *stream, const char *track) {
    size_t offset = 0;
    offset += snprintf(buf + offset, len - offset, "m=video 1 RTP/SAVPF");
    for (auto codec : codecs->codecs) {
        if (rtc_codec_type(codec->id) == rtc_codec_type_audio) continue;
        if (!enable_fec && codec->id == rtc_codec_id_ulpfec) continue;
        if (!enable_fec && codec->id == rtc_codec_id_red) continue;
        offset += snprintf(buf + offset, len - offset, " %d", codec->pt);
        if (codec->rtx_enabled) offset += snprintf(buf + offset, len - offset, " %d", codec->rtx_pt);
    }
    offset += snprintf(buf + offset, len - offset, "\r\n");
    if (bitrate_max > 0) offset += snprintf(buf + offset, len - offset, "b=AS:%d\r\n", bitrate_max);
    offset += snprintf(buf + offset, len - offset, "a=rtcp:9 IN IP4 0.0.0.0\r\n");
    // chrome:
    //   offset += snprintf(buf + offset, len - offset, "a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n");
    // firefox:
    //   offset += snprintf(buf + offset, len - offset, "a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n");
    if (extens) {
        extens->for_each([&buf, &len, &offset](RtpExtensionType type, int id) {
            offset += snprintf(buf + offset, len - offset, "a=extmap:%d %s\r\n", id, RtpExtensionMap::uris()[type]);
        });
    }
    offset += snprintf(buf + offset, len - offset, "a=setup:%s\r\n", setup);
    offset += snprintf(buf + offset, len - offset, "a=mid:%s\r\n", mid);
    offset += snprintf(buf + offset, len - offset, "a=ice-ufrag:%s\r\n", usr);
    offset += snprintf(buf + offset, len - offset, "a=ice-pwd:%s\r\n", pwd);
    offset += snprintf(buf + offset, len - offset, "a=fingerprint:sha-256 %s\r\n", key);
    offset += snprintf(buf + offset, len - offset, "a=%s\r\n", sendrecv);
    if (mux_rtcp) offset += snprintf(buf + offset, len - offset, "a=rtcp-mux\r\n");

    for (auto codec : codecs->codecs) {
        if (rtc_codec_type(codec->id) == rtc_codec_type_audio) continue;
        if (!enable_fec && codec->id == rtc_codec_id_ulpfec) continue;
        if (!enable_fec && codec->id == rtc_codec_id_red) continue;
        offset += snprintf(buf + offset, len - offset, "a=rtpmap:%d %s/%d\r\n", codec->pt, rtc_codec_name(codec->id), codec->clock_rate);
        if (bitrate_min > 0) offset += snprintf(buf + offset, len - offset, "a=fmtp:%d x-google-min-bitrate=%d\r\n", codec->pt, bitrate_min);
        for (auto fb : codec->feedbacks) {
            offset += snprintf(buf + offset, len - offset, "a=rtcp-fb:%d %s\r\n", codec->pt, fb.c_str());
        }
        if (codec->caps.size()) {
            offset += snprintf(buf + offset, len - offset, "a=fmtp:%d ", codec->pt);
            auto cap_it = codec->caps.begin();
            while (cap_it != codec->caps.end()) {
                auto& cap = *cap_it;
                offset += snprintf(buf + offset, len - offset, "%s", cap.c_str());
                ++cap_it;
                if (cap_it != codec->caps.end()) offset += snprintf(buf + offset, len - offset, ";");
            }
            offset += snprintf(buf + offset, len - offset, "\r\n");
        }
        if (codec->rtx_enabled) {
            offset += snprintf(buf + offset, len - offset, "a=rtpmap:%d rtx/%d\r\n", codec->rtx_pt, codec->clock_rate);
            offset += snprintf(buf + offset, len - offset, "a=fmtp:%d apt=%d\r\n", codec->rtx_pt, codec->pt);
        }
    }
    offset += snprintf(buf + offset, len - offset, "a=ssrc-group:FID %u %u\r\n", ssrc_pri, ssrc_rtx);
    offset += snprintf(buf + offset, len - offset, "a=ssrc:%u cname:%s\r\n",     ssrc_pri, cname);
    offset += snprintf(buf + offset, len - offset, "a=ssrc:%u msid:%s %s\r\n",   ssrc_pri, stream, track);
    offset += snprintf(buf + offset, len - offset, "a=ssrc:%u mslabel:%s\r\n",   ssrc_pri, stream);
    offset += snprintf(buf + offset, len - offset, "a=ssrc:%u label:%s\r\n",     ssrc_pri, track);
    offset += snprintf(buf + offset, len - offset, "a=ssrc:%u cname:%s\r\n",     ssrc_rtx, cname);
    offset += snprintf(buf + offset, len - offset, "a=ssrc:%u msid:%s %s\r\n",   ssrc_rtx, stream, track);
    offset += snprintf(buf + offset, len - offset, "a=ssrc:%u mslabel:%s\r\n",   ssrc_rtx, stream);
    offset += snprintf(buf + offset, len - offset, "a=ssrc:%u label:%s\r\n",     ssrc_rtx, track);
    return offset;
}